package com.atguigu.gmall.activity.service.impl;

import com.atguigu.gmall.activity.mapper.SeckillGoodsMapper;
import com.atguigu.gmall.activity.model.OrderRecode;
import com.atguigu.gmall.activity.model.SeckillGoods;
import com.atguigu.gmall.activity.model.UserRecode;
import com.atguigu.gmall.activity.service.SeckillGoodsService;
import com.atguigu.gmall.activity.util.CacheHelper;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.MD5;
import com.atguigu.gmall.order.client.OrderFeignClient;
import com.atguigu.gmall.order.model.OrderDetail;
import com.atguigu.gmall.order.model.OrderInfo;
import com.atguigu.gmall.user.client.UserFeignClient;
import com.atguigu.gmall.user.model.UserAddress;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author: atguigu
 * @create: 2023-01-30 11:47
 */
@Slf4j
@Service
public class SeckillGoodsServiceImpl extends ServiceImpl<SeckillGoodsMapper, SeckillGoods> implements SeckillGoodsService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RabbitService rabbitService;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private OrderFeignClient orderFeignClient;

    /**
     * 查询当日参与秒杀商品列表
     *
     * @return
     */
    @Override
    public List<SeckillGoods> findAll() {
        String redisKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> hashOps = redisTemplate.boundHashOps(redisKey);
        return hashOps.values();
    }


    /**
     * 查询指定秒杀商品信息
     *
     * @param skuId
     * @return
     */
    @Override
    public SeckillGoods getSeckillGoods(Long skuId) {
        String redisKey = RedisConst.SECKILL_GOODS;
        BoundHashOperations<String, String, SeckillGoods> hashOps = redisTemplate.boundHashOps(redisKey);
        return hashOps.get(skuId.toString());
    }

    /**
     * 秒杀请求入队 1.校验抢购码2.校验本地缓存中商品状态3.构建用户购买意向发送消息MQ
     *
     * @param skuId    商品ID
     * @param skuIdStr 用户抢购码
     * @return
     */
    @Override
    public Result seckillRequestToQueue(String userId, Long skuId, String skuIdStr) {
        //1.校验抢购码
        //1.1 按照以前生成规则
        String encrypt = MD5.encrypt(userId + skuId);
        //1.2 判断抢购码是否正确
        if (StringUtils.isBlank(skuIdStr) || !encrypt.equals(skuIdStr)) {
            return Result.build(null, ResultCodeEnum.ILLEGAL_REQUEST);
        }
        //2.校验本地缓存中商品状态
        String state = (String) CacheHelper.get(skuId.toString());
        if (StringUtils.isBlank(state)) {
            return Result.build(null, ResultCodeEnum.ILLEGAL_REQUEST);
        }
        if ("0".equals(state)) {
            return Result.build(null, ResultCodeEnum.SECKILL_FINISH);
        }
        if ("1".equals(state)) {
            //3.构建用户购买意向发送消息MQ
            UserRecode userRecode = new UserRecode();
            userRecode.setUserId(userId);
            userRecode.setSkuId(skuId);
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_USER, MqConst.ROUTING_SECKILL_USER, userRecode);
            return Result.build(null, ResultCodeEnum.SUCCESS);
        }
        return Result.build(null, ResultCodeEnum.SECKILL_RUN);
    }


    /**
     * 处理队列中下单请求，进行秒杀订单预处理
     *
     * @param userRecode
     */
    @Override
    public void processPreSeckillOrder(UserRecode userRecode) {
        //1.二次验证本地缓存中状态
        String skuId = userRecode.getSkuId().toString();
        String userId = userRecode.getUserId();
        String state = (String) CacheHelper.get(skuId);
        if ("0".equals(state)) {
            //商品以及售罄
            return;
        }

        //2.验证用户是否重复下单 setnx
        //允许用户同一时间抢购多件商品
        String key = RedisConst.SECKILL_USER + userId + ":" + skuId;
        //TODO 计算剩余时间 获取下一天00:00 毫秒值-当前系统时间毫秒值
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, skuId, 1, TimeUnit.HOURS);
        if (!flag) {
            //当前商品不允许重复下单
            return;
        }

        //3.校验库存同时移除商品库存List中元素-同时缓存中扣减库存
        String goodsId = (String) redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).rightPop();
        if (StringUtils.isBlank(goodsId)) {
            //当前商品以及售罄 更新 所有的秒杀服务实例本地缓存中商品状态
            redisTemplate.convertAndSend("seckillpush", skuId + ":0");
            return;
        }

        //4.生成临时订单-抢购成功/抢购资格标记;将临时订单存入Redis缓存
        OrderRecode orderRecode = new OrderRecode();
        orderRecode.setUserId(userId);
        orderRecode.setSeckillGoods(this.getSeckillGoods(Long.valueOf(skuId)));
        orderRecode.setNum(1);
        //临时订单订单码 采用MD5生成
        orderRecode.setOrderStr(MD5.encrypt(userId + skuId));
        redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).put(userId, orderRecode);

        //5.发送更新库存消息-异步更新分布式缓存Redis以及数据库Mysql中库存
        rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_STOCK, MqConst.ROUTING_SECKILL_STOCK, skuId);
    }

    /**
     * 扣减秒杀库存 缓存Redis以及MySQL
     *
     * @param skuId
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateStock(Long skuId) {
        //1.根据SKUID查询剩余的库存数量
        Long count = redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).size();
        //2.根据SKUID查询秒杀商品-等同于从数据库中查询
        SeckillGoods seckillGoods = this.getSeckillGoods(skuId);
        seckillGoods.setStockCount(count.intValue());
        //3.更新缓存Redis中秒杀商品
        redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).put(skuId.toString(), seckillGoods);

        //4.更新数据库中秒杀商品数量
        this.updateById(seckillGoods);

    }

    /**
     * 检查秒杀订单-秒杀结果
     * 抢单成功 215 - 用户在队列中，临时订单hash结果中有用户ID 返回临时订单信息
     * 下单成功 218 - 用户秒杀商品成功并且提交秒杀订单 从用户秒杀订单缓存hash中获取
     * 商品售罄 213 - 以上没有满足 且 本地缓存中订单状态位 0
     * 正在排队中 211
     *
     * @param skuId
     * @return
     */
    @Override
    public Result checkSeckillResult(String userId, Long skuId) {
        //1.判断用户是否在秒杀队列  判断当前用户对于某件商品秒杀是否产生临时订单
        Boolean flag = redisTemplate.hasKey(RedisConst.SECKILL_USER + userId + ":" + skuId);
        if (flag) {
            //用户在队列中 有机会抢购成功（获取提交秒杀订单机会）
            BoundHashOperations<String, String, OrderRecode> hashOps = redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS);
            Boolean hasKey = hashOps.hasKey(userId);
            if (hasKey) {
                //用户抢单成功-响应秒杀订单信息
                OrderRecode orderRecode = hashOps.get(userId);
                return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);
            }
        }
        //2.判断秒杀成功订单中是否包含当前用户
        BoundHashOperations<String, String, String> orderSuccessHashOps = redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS);
        Boolean orderSuccess = orderSuccessHashOps.hasKey(userId);
        if (orderSuccess) {
            //说明下单成功 响应订单ID 页面中展示去订单页面进行支付
            String orderId = orderSuccessHashOps.get(userId);
            return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);
        }

        //3.判断本地缓存中状态位
        String state = (String) CacheHelper.get(skuId.toString());  //null
        if ("0".equals(state)) {
            return Result.build(null, ResultCodeEnum.SECKILL_FINISH);
        }

        //4.其他情况显示排队中
        return Result.build(null, ResultCodeEnum.SECKILL_RUN);
    }

    /**
     * 秒杀订单确认页面渲染需要参数汇总接口
     *
     * @return
     */
    @Override
    public Result seckillTradeData(String userId) {
        //1.根据用户ID查询 临时订单信息包含商品信息
        BoundHashOperations<String, String, OrderRecode> hashOps = redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS);
        //1.1 获取临时订单
        OrderRecode orderRecode = hashOps.get(userId);
        if (orderRecode == null) {
            return Result.build(null, ResultCodeEnum.ILLEGAL_REQUEST);
        }
        //1.2 获取秒杀商品
        SeckillGoods seckillGoods = orderRecode.getSeckillGoods();
        if (seckillGoods == null) {
            return Result.build(null, ResultCodeEnum.ILLEGAL_REQUEST);
        }

        //2.构建秒杀订单对象以及秒杀商品明细集合
        //2.1 构建订单
        OrderInfo orderInfo = new OrderInfo();
        //2.2 构建订单明细
        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setSkuName(seckillGoods.getSkuName());
        orderDetail.setOrderPrice(seckillGoods.getCostPrice());
        orderDetail.setImgUrl(seckillGoods.getSkuDefaultImg());
        orderDetail.setSkuId(seckillGoods.getSkuId());
        orderDetail.setSkuNum(orderRecode.getNum());
        List<OrderDetail> orderDetails = Arrays.asList(orderDetail);
        orderInfo.setOrderDetailList(orderDetails);
        //计算订单总金额
        orderInfo.sumTotalAmount();

        //3.远程调用用户微服务得到收件地址列表
        List<UserAddress> userAddressList = userFeignClient.findUserAddressListByUserId(Long.valueOf(userId));


        //4.封装响应数据Map
        HashMap<String, Object> resultMap = new HashMap<>();
        resultMap.put("detailArrayList", orderDetails);
        resultMap.put("totalNum", orderDetails.size());
        resultMap.put("totalAmount", orderInfo.getTotalAmount());
        resultMap.put("userAddressList", userAddressList);
        return Result.ok(resultMap);
    }


    /**
     * 保存秒杀订单
     *
     * @param orderInfo
     * @return
     */
    @Override
    public Result submitSeckillOrder(OrderInfo orderInfo) {
        Long userId = orderInfo.getUserId();
        //1.远程调用订单微服务保存秒杀订单
        Long orderId = orderFeignClient.submitSeckillOrder(orderInfo);

        //2.删除Redis中临时订单
        redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).delete(userId.toString());

        //3.将用户下单记录存入缓存Redis
        redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).put(userId.toString(), orderId.toString());

        //4.返回保存后订单ID - 跟支付页面进行对接
        return Result.ok(orderId);
    }

    /**
     * 18:00 清理秒杀产生相关缓存数据，更新数据库中秒杀商品状态
     */
    @Override
    public void closeSeckillOrderAndClearCache() {
        //1.从数据查询当日已经结束的秒杀商品
        LambdaQueryWrapper<SeckillGoods> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SeckillGoods::getStatus, 1);
        queryWrapper.lt(SeckillGoods::getEndTime, new Date());
        List<SeckillGoods> seckillGoodsList = this.list(queryWrapper);

        //2.遍历结束秒杀商品清理缓存
        //2.1 删除所有的秒杀商品hash
        redisTemplate.delete(RedisConst.SECKILL_GOODS);
        //2.2 删除所有秒杀订单
        redisTemplate.delete(RedisConst.SECKILL_ORDERS_USERS);
        if (!CollectionUtils.isEmpty(seckillGoodsList)) {
            for (SeckillGoods seckillGoods : seckillGoodsList) {
                //2.2 删除当前商品库存缓存
                redisTemplate.delete(RedisConst.SECKILL_STOCK_PREFIX + seckillGoods.getSkuId());
                //3.更新数据库中秒杀商品状态
                seckillGoods.setStatus("2");
                this.updateById(seckillGoods);
            }
        }
    }
}
